contents
1. 예외 처리란?
- 예외 처리(Exception Handling) 는 프로그램 실행 중 발생하는 오류나 예측 불가능한 상황을 관리하여, 프로그램이 비정상적으로 종료되는 것을 방지하고 보다 우아하게 대처할 수 있도록 하는 메커니즘입니다.
- 안정성, 사용자 친화적인 피드백, 비즈니스 로직과 에러 처리 코드의 분리 등의 목적을 가집니다.
2. 자바 예외 모델
A. 예외 계층 구조
- 모든 예외는
Throwable클래스로부터 파생됩니다.- Error: 시스템 치명적 오류(OutOfMemoryError 등), 일반적으로 catch하지 않음.
- Exception: 복구 가능한 오류, 세분화하면
- 체크 예외(Checked Exception): 반드시 선언하거나 처리해야 함(IOException, SQLException 등).
- 언체크 예외(UncheckedException, RuntimeException): 논리적 프로그래밍 오류, 컴파일 시 강제되지 않음(NullPointerException, ArithmeticException 등).
B. 예외 처리 흐름
- 발생(Throwing): 오류 발생 시 예외 객체가 생성되고 "던져짐(throw)".
- 포착(Catching): JVM은 call 스택을 따라 적합한
catch블록을 탐색하여 처리.- 핸들러가 없으면 프로그램이 종료되고 스택 트레이스가 출력됨.
3. 핵심 키워드
try: 예외가 발생할 가능성이 있는 코드 영역.catch: 예외 발생 시 해당 예외를 처리.finally: 반드시 실행(자원 해제, cleanup 등).throw: 예외를 명시적으로 던짐.throws: 메서드가 발생 가능한 예외를 선언.
4. 예시
try {
int[] arr = new int;
int i = arr; // ArrayIndexOutOfBoundsException 발생
} catch (ArrayIndexOutOfBoundsException ex) {
System.out.println("예외 처리됨: " + ex);
} finally {
System.out.println("항상 실행됨.");
}
체크 예외 예시
public void readFile(String path) throws IOException {
FileReader reader = new FileReader(path); // IOException 처리 또는 선언 필수
}
언체크 예외 예시
String x = null;
System.out.println(x.length()); // NullPointerException 발생(언체크)
5. 사용자 정의 예외 클래스
직접 예외 클래스를 정의할 수 있습니다:
public class InvalidOrderException extends RuntimeException {
public InvalidOrderException(String message) {
super(message);
}
}
6. 스프링 부트에서의 예외 처리
-
@ExceptionHandler: 컨트롤러에서 특정 예외를 처리하는 메서드 구현.
@ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<String> handleNotFound(ResourceNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); } -
@ControllerAdvice / @RestControllerAdvice: 전역적으로 예외 처리를 담당하는 클래스(공통 에러 처리).
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ApiError> handleAll(Exception ex) { ... } } -
베스트 프랙티스:
- 도메인/비즈니스 오류에 대한 커스텀 예외 클래스 적극 사용.
- 일관성과 중앙 제어를 위해 글로벌 핸들러 구현 권장.
- HTTP 상태 코드와 사용자 정의 에러 응답 매핑.
7. 베스트 프랙티스
- 자원(커넥션 등)은
finally또는 try-with-resources로 꼭 해제. - 비즈니스 로직은 커스텀 예외로 분리해 처리.
- 모든 Exception을 무분별하게 catch하지 말고, 경계(최상위)에서 처리를 권장.
- 입력값은 빠르게 검증 후 예외를 던짐.
- 예외는 로깅 및 모니터링이 중요.
- 의미 있는 컨텍스트가 추가될 경우에만 감싸서 재던짐.
- API 응답에서는 내부 상세 정보를 숨김.
8. 요약 비교표
| 개념 | 체크 예외 | 언체크(런타임) 예외 |
|---|---|---|
| 처리 필수 여부 | 예 | 아니오 |
| 메서드 선언 필요 | throws로 선언 |
선택적으로 선언 가능 |
| 주요 예시 | IOException, SQLException | NullPointerException, IndexOutOfBoundsException |
요약:
자바와 스프링의 예외 처리는 오류를 감지·복구·전달하여 유지보수성과 안정성, 사용자 친화성을 높여줍니다. 핵심 로직 분리, 적절한 예외 클래스를 통한 관리, 모니터링 및 적절한 오류 응답으로 견고한 소프트웨어 개발에 필수적입니다.
Spring Boot에서 자주 사용되는 에러 처리 방식을 보겠습니다.
1. 기본 에러 응답(Spring Boot)
Spring Boot에서 에러가 별도로 처리되지 않을 경우, 기본적으로 아래와 같이 JSON 형태의 에러 응답이 반환됩니다:
{
"timestamp": "2019-09-16T22:14:45.624+0000",
"status": 404,
"error": "Not Found",
"message": "Book with id 1 not found",
"path": "/api/book/1"
}
이 구조는 이벤트 시각, HTTP 상태 코드, 에러 명칭, 메시지, 요청 경로를 포함합니다.
2. 커스텀 글로벌 예외 처리
전체 컨트롤러에서 응답 구조를 일관성 있게 커스터마이즈하려면 @RestControllerAdvice 또는 @ControllerAdvice와 @ExceptionHandler를 활용합니다.
커스텀 에러 객체 예시:
public class ApiError {
private String type;
private String title;
private int status;
private String detail;
private String instance;
// 생성자, getter, setter 등
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(ResourceNotFoundException ex, HttpServletRequest request) {
ApiError error = new ApiError(
"/errors/resource-not-found",
"Resource Not Found",
404,
ex.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(404).body(error);
}
}
이 방식으로 일관된 구조의 에러 응답을 만듭니다.
3. RFC 7807 및 ProblemDetail 활용
Spring Boot 3+ 최신 API는 RFC 7807("HTTP API의 표준 에러 응답") 형식도 적극 사용합니다.
예시 구조:
{
"type": "/errors/incorrect-user-pass",
"title": "Incorrect username or password.",
"status": 401,
"detail": "Authentication failed due to incorrect username or password.",
"instance": "/login/log/abc123"
}
- type: 에러 유형을 나타내는 URI
- title: 간략 요약 메시지
- status: HTTP 상태 코드
- detail: 구체적 설명
- instance: 해당 에러 발생 API 경로 등
4. ProblemDetail 활용(Spring Boot 3+)
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.*;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ProblemDetail handleNotFound(ResourceNotFoundException ex, HttpServletRequest request) {
ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
detail.setTitle("Resource Not Found");
detail.setDetail(ex.getMessage());
detail.setInstance(URI.create(request.getRequestURI()));
detail.setType(URI.create("/errors/not-found"));
return detail;
}
}
표준화된 머신 읽기/쓰기 가능한 에러 응답을 제공합니다.
5. 베스트 프랙티스
- 실제 HTTP 상태 코드(400, 404, 422, 500 등)를 정확히 반환.
- 명확하고 사람이 이해하기 쉬운 메시지 제공.
- 민감한 내부 정보는 에러 응답에 노출하지 않기.
- 가능하면 RFC 7807 표준에 맞게 에러 객체 구성.
요약:
Spring 기반 웹 애플리케이션은 @ControllerAdvice 등 중앙화 예외 처리로 모든 에러를 예측 가능한 구조의 JSON 응답으로 변환하며, 커스텀 에러 객체 혹은 ProblemDetail 같은 표준 객체를 이용해 디버깅과 API 사용성을 향상시킵니다.
references